package org.lhh.server.core.codec.trans;

import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.util.mxDomUtils;
import com.mxgraph.util.mxUtils;
import com.mxgraph.util.mxXmlUtils;
import com.mxgraph.view.mxGraph;
import org.apache.commons.lang3.StringUtils;
import org.lhh.server.common.utils.*;
import org.lhh.server.config.web.exception.EtlException;
import org.lhh.server.core.codec.cluster.ClusterSchemaCodec;
import org.lhh.server.core.codec.database.DatabaseCodec;
import org.lhh.server.core.codec.slave.SlaveServerCodec;
import org.lhh.server.core.codec.steps.StepEncoder;
import org.lhh.server.core.repository.EtlRepository;
import org.pentaho.di.base.AbstractMeta;
import org.pentaho.di.cluster.ClusterSchema;
import org.pentaho.di.cluster.SlaveServer;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.gui.Point;
import org.pentaho.di.core.logging.StepLogTable;
import org.pentaho.di.core.logging.TransLogTable;
import org.pentaho.di.core.parameters.DuplicateParamException;
import org.pentaho.di.core.parameters.UnknownParamException;
import org.pentaho.di.core.plugins.PluginInterface;
import org.pentaho.di.core.plugins.PluginRegistry;
import org.pentaho.di.core.plugins.StepPluginType;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.partition.PartitionSchema;
import org.pentaho.di.repository.LongObjectId;
import org.pentaho.di.trans.TransDependency;
import org.pentaho.di.trans.TransHopMeta;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
import org.pentaho.di.trans.steps.missing.MissingTrans;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public abstract class TransMetaCodec {

    public static String encode(AbstractMeta meta) {
        mxGraph graph = new mxGraph();
        graph.getModel().beginUpdate();
        mxCell parent = (mxCell) graph.getDefaultParent();
        Document doc = mxDomUtils.createDocument();
        Element e = doc.createElement("Trans");
        TransMeta transMeta = (TransMeta) meta;
        //数据连接
        List<DatabaseMeta> databases = transMeta.getDatabases();
        List<DatabaseCodec> databaseCodecList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(databases)) {
            for (DatabaseMeta databaseMeta : databases) {
                databaseCodecList.add(DatabaseCodec.encode(databaseMeta));
            }
        }
        e.setAttribute("databases", JsonUtils.beanToJson(databaseCodecList));
        //转换设置
        //转换
        e.setAttribute("objectId", transMeta.getObjectId().getId());
        e.setAttribute("name", transMeta.getName());
        e.setAttribute("fileName", transMeta.getFilename());
        e.setAttribute("description", transMeta.getDescription());
        e.setAttribute("extended_description", transMeta.getExtendedDescription());
        e.setAttribute("trans_version", transMeta.getTransversion());
        e.setAttribute("trans_status", String.valueOf(transMeta.getTransstatus()));
        e.setAttribute("directory", transMeta.getRepositoryDirectory().getObjectId().getId());
        e.setAttribute("created_user", transMeta.getCreatedUser());
        e.setAttribute("created_date", XMLHandler.date2string(transMeta.getCreatedDate()));
        e.setAttribute("modified_user", transMeta.getModifiedUser());
        e.setAttribute("modified_date", XMLHandler.date2string(transMeta.getModifiedDate()));
        //参数命名
        String[] parameters = transMeta.listParameters();
        JSONArray jsonArray = new JSONArray();
        if (!CollectionUtils.isEmpty(parameters)) {
            for (String parameter : parameters) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("key", parameter);
                try {
                    jsonObject.put("value", meta.getParameterDefault(parameter));
                    jsonObject.put("description", meta.getParameterDescription(parameter));
                } catch (UnknownParamException ex) {
                    ex.printStackTrace();
                }
                jsonArray.add(jsonObject);
            }
        }
        e.setAttribute("parameters", jsonArray.toString());
        //日志
        TransLogTable transLogTable = transMeta.getTransLogTable();
        JSONObject transLogObj = new JSONObject();
        transLogObj.put("connection", transLogTable.getConnectionName());
        transLogObj.put("table", transLogTable.getTableName());
        transLogObj.put("interval", transLogTable.getLogInterval());
        e.setAttribute("transLogTable", transLogObj.toString());
        StepLogTable stepLogTable = transMeta.getStepLogTable();
        JSONObject stepLogObj = new JSONObject();
        stepLogObj.put("connection", stepLogTable.getConnectionName());
        stepLogObj.put("table", stepLogTable.getTableName());
        e.setAttribute("stepLogTable", stepLogObj.toString());
        //依赖
        List<TransDependencyCodec> transDependencyCodecs = TransDependencyCodec.encode(transMeta.getDependencies());
        e.setAttribute("dependencies", JsonUtils.beanToJson(transDependencyCodecs));
        //日期
        e.setAttribute("connection", transMeta.getMaxDateConnection() == null ? "" : transMeta.getMaxDateConnection().getName());
        e.setAttribute("table", transMeta.getMaxDateTable());
        e.setAttribute("field", transMeta.getMaxDateField());
        e.setAttribute("offset", String.valueOf(transMeta.getMaxDateOffset()));
        e.setAttribute("maxdiff", String.valueOf(transMeta.getMaxDateDifference()));
        //杂项
        e.setAttribute("size_rowset", String.valueOf(transMeta.getSizeRowset()));
        e.setAttribute("feedback_shown", transMeta.isFeedbackShown() ? "Y" : "N");
        e.setAttribute("feedback_size", String.valueOf(transMeta.getFeedbackSize()));
        e.setAttribute("unique_connections", transMeta.isUsingUniqueConnections() ? "Y" : "N");
        e.setAttribute("using_thread_priorities", transMeta.isUsingThreadPriorityManagment() ? "Y" : "N");
        e.setAttribute("shared_objects_file", meta.getSharedObjectsFile());
        e.setAttribute("trans_type", transMeta.getTransformationType().getCode());
        //监控
        e.setAttribute("capture_step_performance", "Y");
        e.setAttribute("step_performance_capturing_delay", "2000");
        e.setAttribute("step_performance_capturing_size_limit", "20");
        //子服务器
        List<SlaveServerCodec> slaveServerCodecs = SlaveServerCodec.encode(transMeta.getSlaveServers());
        e.setAttribute("slaveServers", JsonUtils.beanToJson(slaveServerCodecs));
        //集群
        List<ClusterSchemaCodec> clusterSchemaCodecs = ClusterSchemaCodec.encode(transMeta.getClusterSchemas());
        e.setAttribute("clusterSchemas", JsonUtils.beanToJson(clusterSchemaCodecs));
        //数据库分区
        List<PartitionSchemaCodec> PartitionSchemaCodecs = PartitionSchemaCodec.encode(transMeta.getPartitionSchemas());
        e.setAttribute("partitionSchemas", JsonUtils.beanToJson(PartitionSchemaCodecs));
        parent.setValue(e);
        //步骤
        HashMap<StepMeta, Object> cells = new HashMap<StepMeta, Object>();
        List<StepMeta> stepMetas = transMeta.getSteps();
        List<TransHopMeta> transHops = transMeta.getTransHops();
        for (StepMeta stepMeta : stepMetas) {
            Point p = stepMeta.getLocation();
            StepEncoder stepEncoder = (StepEncoder) SpringBeanUtil.getBean(stepMeta.getStepID());
            PluginInterface plugin = PluginRegistry.getInstance().getPlugin(StepPluginType.class, stepMeta.getStepID());
            Object cell = graph.insertVertex(parent, null, stepEncoder.encodeStep(stepMeta), p.x, p.y, ImageUtil.IMAGE_WIDTH, ImageUtil.IMAGE_HEIGHT, "node;image=" + ImageUtil.getURl(plugin.getImageFile()));
            cells.put(stepMeta, cell);
        }
        for (TransHopMeta transHopMeta : transHops) {
            Object fromStep = cells.get(transHopMeta.getFromStep());
            Object toStep = cells.get(transHopMeta.getToStep());
            graph.insertEdge(parent, null, null, fromStep, toStep, null);
        }
        graph.getModel().endUpdate();
        mxCodec codec = new mxCodec();
        return mxUtils.getPrettyXml(codec.encode(graph.getModel()));
    }

    public static AbstractMeta decode(String graphXml) {
        mxGraph graph = new mxGraph();
        mxCodec codec = new mxCodec();
        Document doc = mxXmlUtils.parseXml(graphXml);
        codec.decode(doc.getDocumentElement(), graph.getModel());
        mxCell root = (mxCell) graph.getDefaultParent();
        TransMeta transMeta = new TransMeta();
        //转换
        //数据连接
        String databases = root.getAttribute("databases");
        List<DatabaseCodec> databaseCodecList = JsonUtils.jsonToList(databases, DatabaseCodec.class);
        if (!CollectionUtils.isEmpty(databaseCodecList)) {
            for (DatabaseCodec databaseCodec : databaseCodecList) {
                transMeta.addDatabase(DatabaseCodec.decode(databaseCodec));
            }
        }
        //转换
        //转换
        transMeta.setObjectId(new LongObjectId(Long.valueOf(root.getAttribute("objectId"))));
        transMeta.setName(root.getAttribute("name"));
        transMeta.setFilename(root.getAttribute("fileName"));
        transMeta.setDescription(root.getAttribute("description"));
        transMeta.setExtendedDescription(root.getAttribute("extended_description"));
        transMeta.setTransversion(root.getAttribute("trans_version"));
        transMeta.setTransstatus(Integer.valueOf(root.getAttribute("trans_status")));
        transMeta.setRepositoryDirectory(EtlRepository.getDirectory(new LongObjectId(Long.valueOf(root.getAttribute("directory")))));
        transMeta.setCreatedUser(root.getAttribute("created_user"));
        transMeta.setCreatedDate(XMLHandler.stringToDate(root.getAttribute("created_date")));
        transMeta.setModifiedUser(root.getAttribute("modified_user"));
        transMeta.setModifiedDate(XMLHandler.stringToDate(root.getAttribute("modified_date")));
        //参数命名
        JSONArray jsonArray = JSONArray.fromObject(root.getAttribute("parameters"));
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String key = jsonObject.optString("key");
            String value = jsonObject.optString("value");
            String description = jsonObject.optString("description");
            if (!(StringUtils.isEmpty(key) || StringUtils.isEmpty(value))) {
                try {
                    transMeta.addParameterDefinition(key, value, description);
                } catch (DuplicateParamException ignored) {
                }
            }
        }
        //日期
        DatabaseMeta databaseMeta = transMeta.findDatabase(root.getAttribute("connection"));
        transMeta.setMaxDateConnection(databaseMeta);
        transMeta.setMaxDateTable(root.getAttribute("table"));
        transMeta.setMaxDateField(root.getAttribute("field"));
        transMeta.setMaxDateOffset(Double.valueOf(root.getAttribute("offset")));
        transMeta.setMaxDateDifference(Double.valueOf(root.getAttribute("maxdiff")));
        //日志
        JSONObject transLogObj = JSONObject.fromObject(root.getAttribute("transLogTable"));
        TransLogTable transLogTable = transMeta.getTransLogTable();
        transLogTable.setConnectionName(transLogObj.optString("connection"));
        transLogTable.setTableName(transLogObj.optString("table"));
        transLogTable.setLogInterval(transLogObj.optString("interval"));

        JSONObject stepLogObj = JSONObject.fromObject(root.getAttribute("stepLogTable"));
        StepLogTable stepLogTable = transMeta.getStepLogTable();
        stepLogTable.setConnectionName(stepLogObj.optString("connection"));
        stepLogTable.setTableName(stepLogObj.optString("table"));
        //依赖
        List<TransDependency> transDependencies = TransDependencyCodec.decode(transMeta, JsonUtils.jsonToList(root.getAttribute("dependencies"), TransDependencyCodec.class));
        transMeta.setDependencies(transDependencies);
        //杂项
        transMeta.setSizeRowset(Const.toInt(root.getAttribute("size_rowset"), Const.ROWS_IN_ROWSET));
        transMeta.setFeedbackShown(!"N".equalsIgnoreCase(root.getAttribute("feedback_shown")));
        transMeta.setFeedbackSize(Const.toInt(root.getAttribute("feedback_size"), Const.ROWS_UPDATE));
        transMeta.setUsingUniqueConnections("Y".equalsIgnoreCase(root.getAttribute("unique_connections")));
        transMeta.setUsingThreadPriorityManagment(!"N".equalsIgnoreCase(root.getAttribute("using_thread_priorities")));
        transMeta.setSharedObjectsFile(root.getAttribute("shared_objects_file"));
        transMeta.setTransformationType(TransMeta.TransformationType.getTransformationTypeByCode(root.getAttribute("trans_type")));
        //监控
        transMeta.setCapturingStepPerformanceSnapShots("Y".equalsIgnoreCase(root.getAttribute("capture_step_performance")));
        transMeta.setStepPerformanceCapturingDelay(Const.toLong(root.getAttribute("step_performance_capturing_delay"), 2000));
        transMeta.setStepPerformanceCapturingSizeLimit(root.getAttribute("step_performance_capturing_size_limit"));
        //子服务器
        List<SlaveServer> slaveServers = SlaveServerCodec.decode(JsonUtils.jsonToList(root.getAttribute("slaveServers"), SlaveServerCodec.class));
        transMeta.setSlaveServers(slaveServers);
        //集群
        List<ClusterSchema> clusterSchemas = ClusterSchemaCodec.decode(JsonUtils.jsonToList(root.getAttribute("clusterSchemas"), ClusterSchemaCodec.class));
        transMeta.setClusterSchemas(clusterSchemas);
        //数据库分区
        List<PartitionSchema> partitionSchemas = PartitionSchemaCodec.decode(JsonUtils.jsonToList(root.getAttribute("partitionSchemas"), PartitionSchemaCodec.class));
        transMeta.setPartitionSchemas(partitionSchemas);

        int count = graph.getModel().getChildCount(root);
        for (int i = 0; i < count; i++) {
            mxCell cell = (mxCell) graph.getModel().getChildAt(root, i);
            if (cell.isVertex()) {
                Element e = (Element) cell.getValue();
                if ("Step".equals(e.getTagName())) {
                    StepEncoder stepEncoder = (StepEncoder) SpringBeanUtil.getBean(cell.getAttribute("pluginId"));
                    StepMeta stepMeta = stepEncoder.decodeStep(transMeta, cell, transMeta.getDatabases(), transMeta.getMetaStore());
                    stepMeta.setParentTransMeta(transMeta);
                    if (stepMeta.isMissing()) {
                        transMeta.addMissingTrans((MissingTrans) stepMeta.getStepMetaInterface());
                    }
                    StepMeta check = transMeta.findStep(stepMeta.getName());
                    if (check != null) {
                        throw new EtlException(check.getName() + "名称存在重复");
                    } else {
                        transMeta.addStep(stepMeta);
                    }
                }
            }
        }

        for (int i = 0; i < transMeta.nrSteps(); i++) {
            StepMeta stepMeta = transMeta.getStep(i);
            StepMetaInterface stepMetaInterface = stepMeta.getStepMetaInterface();
            if (stepMetaInterface != null) {
                stepMetaInterface.searchInfoAndTargetSteps(transMeta.getSteps());
            }
        }

        for (int i = 0; i < count; i++) {
            mxCell cell = (mxCell) graph.getModel().getChildAt(root, i);
            if (cell.isEdge()) {
                mxCell source = (mxCell) cell.getSource();
                mxCell target = (mxCell) cell.getTarget();
                TransHopMeta hopinf = new TransHopMeta(null, null, true);
                String[] stepNames = transMeta.getStepNames();
                for (int j = 0; j < stepNames.length; j++) {
                    if (stepNames[j].equalsIgnoreCase(source.getAttribute("label")))
                        hopinf.setFromStep(transMeta.getStep(j));
                    if (stepNames[j].equalsIgnoreCase(target.getAttribute("label")))
                        hopinf.setToStep(transMeta.getStep(j));
                }
                transMeta.addTransHop(hopinf);
            }
        }
        return transMeta;
    }
}
